package puzzle.devtools;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.prefs.Preferences;
import javax.imageio.ImageIO;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;
import org.lwjgl.LWJGLException;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
import puzzle.core.Direction;
import puzzle.core.Directional;
import puzzle.core.LevelData;
import puzzle.core.LevelMap;
import puzzle.core.Tile;
import puzzle.core.TileFactory;
import puzzle.core.TileType;
import puzzle.core.tiles.PortTile;
import puzzle.devtools.AbstractEditor;
import puzzle.devtools.LevelEditor;
import puzzle.devtools.SlickCanvas;
import puzzle.util.MapStream;
import puzzle.util.ResourceManager;
import puzzle.util.SpringUtilities;

public class LevelEditor extends AbstractEditor {
  
  private static Preferences prefs = null;
  static {
    try {
      prefs = Preferences.userNodeForPackage(LevelEditor.class);
    }
    catch (Exception e) {
      Log.warn("Can't use Prefs");
    }
  }
  
  public static void main(String[] args) {
    boolean debug = (args.length>=1 && args[0].equals("-d"));
    Log.setVerbose(debug);
    
    //whether we should print to file
    if (debug) {
      try { Log.out = new PrintStream("debug.txt"); }
      catch (FileNotFoundException e) {}
    }
        
    LevelEditor.loadUI();
     
    try {
      new LevelEditor();
    }
    catch (Exception e) {
      Log.error(e);
    }
  }
  
  static {
    JPopupMenu.setDefaultLightWeightPopupEnabled(false);
  }
  
  private EditorCanvas canvas = null;
  private Tile current = null;
  private Direction currentDir = null;
  private JComboBox brushCombo = null;
  private boolean startDown = false;
  private java.awt.Cursor emptyCursor = null;
  private File currentFile = null;
  private boolean fileChanged = false;
  private JLabel dirLabel = null;
  private PortManager portMgr = null;
  private TileFactory tileFactory = null;
  private MapStream mapStream = null;
  private LevelInfoDialog infoDialog = null;
  
  public LevelEditor() throws LWJGLException {
    super();
    setTitle(getEditorName());
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setResizable(false);
    
    getJFileChooser().setAcceptAllFileFilterUsed(true); 
    getJFileChooser().setFileFilter(new BPuzFileFilter());
    
    portMgr = new PortManager();
    infoDialog = new LevelInfoDialog();
    
    JPanel content = new JPanel();

    KeyListener keyl = new KeyAdapter() { 
      public void keyPressed(KeyEvent e) {
        if (current==null || tileFactory==null)
          return;
        
        if (e.getKeyCode()==KeyEvent.VK_R)
          rotateCurrent();
      }
    };
    addKeyListener(keyl); 
    
    JPanel tools = createToolbar();
    
    canvas = new EditorCanvas();
    canvas.setSize(LevelMap.PIXEL_WIDTH, LevelMap.PIXEL_HEIGHT);
    
    canvas.addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent e) {
        if (current==null || tileFactory==null)
          return;
        
        boolean placing = false;
        if (SwingUtilities.isLeftMouseButton(e))
          placing = true;
        else if (SwingUtilities.isRightMouseButton(e))
          placing = false;
        
        handleMouseEvent(placing, e.getX(), e.getY());
      }
    });
    canvas.addMouseMotionListener(new MouseMotionAdapter() {
      public void mouseDragged(MouseEvent e) {
        if (current==null || tileFactory==null)
          return;
          
        boolean placing = false;
        if (SwingUtilities.isLeftMouseButton(e))
          placing = true;
        else if (SwingUtilities.isRightMouseButton(e))
          placing = false;
        
        if (current.getTileType()!=TileType.PORT) 
          handleMouseEvent(placing, e.getX(), e.getY());
      }
      
      public void mouseMoved(MouseEvent e) {
        if (current==null || tileFactory==null)
          return;
          
        portMgr.handleMouseMove(e.getX(), e.getY());
      }
    });
    canvas.setBackground(java.awt.Color.darkGray);
    canvas.addKeyListener(keyl);
    brushCombo.addKeyListener(keyl); 
    
    canvas.setCursor(getEmptyCursor());
    
    content.add(canvas);
    
    //getContentPane().setLayout(new FlowLayout()); 
    getContentPane().add(tools, BorderLayout.NORTH);
    getContentPane().add(content, BorderLayout.CENTER);
    
    initMenuBar();
    
    updateTitle();
    
    setJMenuBar(getJMenuBar());
    pack();
    setLocationRelativeTo(null);
    setVisible(true);
    toFront();
    brushCombo.grabFocus();
  }
  
  public String getPrefix() {
    return "editor";
  }
  
  private JPanel createToolbar() {
    int dirIndex = (getPrefs()!=null) 
                ? getPrefs().getInt("direction.index", Direction.UP.ordinal())
                : Direction.UP.ordinal();
    currentDir = Direction.values()[dirIndex];
    
    brushCombo = new JComboBox(TileType.list());    
    int brushIndex = (getPrefs()!=null)
                ? getPrefs().getInt("brush.index", TileType.START.ordinal())
                : TileType.START.ordinal();
    brushCombo.setSelectedIndex(brushIndex);
    
    brushCombo.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (current==null || tileFactory==null)
          return;
        changeBrush();
      }
    });
    
    final String txt = "A level creation tool for BallPuzzle developers, "
                       +"created by davedes.\n\n"
                       +"Left Mouse Button: Paint with current brush\n"
                       +"Right Mouse Button: Erase\n"
                       +"Press R to change direction.";
    
    final JTextArea ta = new JTextArea(5, 50);
    ta.setWrapStyleWord(true);
    ta.setLineWrap(true);
    ta.setText(txt);
    ta.setFont(new java.awt.Font("Verdana", java.awt.Font.PLAIN, 10));
    ta.setEditable(false);
    
    JPanel textp = new JPanel();
    textp.add(ta);
    
    JLabel brushLabel = new JLabel("Brush:");
    
    JPanel brushPanel = new JPanel();
    
    dirLabel = new JLabel("Direction: "+currentDir.toString());
    
    brushPanel.add(brushLabel);
    brushPanel.add(brushCombo);
    brushPanel.add(Box.createRigidArea(new Dimension(15, 0)));
    brushPanel.add(dirLabel);
        
    JPanel tools = new JPanel();
    tools.setLayout(new BoxLayout(tools, BoxLayout.PAGE_AXIS));
    tools.add(textp);
    tools.add(Box.createRigidArea(new Dimension(0, 5)));
    tools.add(brushPanel);
    
    return tools;
  }
  
  public java.awt.Cursor getEmptyCursor() {
    if (emptyCursor!=null)
      return emptyCursor;
     
    //else, create custom cursor
    Toolkit tk = Toolkit.getDefaultToolkit();
    Point hotspot = new Point(Tile.WIDTH/2, Tile.HEIGHT/2);
    java.awt.Image cimg = new BufferedImage(
                        Tile.WIDTH, Tile.HEIGHT, BufferedImage.TYPE_INT_ARGB);
    emptyCursor = tk.createCustomCursor(cimg, hotspot, "EMPTY");
    return emptyCursor;
  }
  
  public void setMouseCursor(String ref, int hotSpotX, int hotSpotY) throws SlickException { 
    java.awt.Toolkit tk = java.awt.Toolkit.getDefaultToolkit(); 
    java.awt.Point hotspot = new java.awt.Point(hotSpotX, hotSpotY); 
    
    java.awt.Image img;
    
    //use slick resource loading 
    try {
      InputStream in = ResourceLoader.getResourceAsStream(ref); 
      img = javax.imageio.ImageIO.read(in); 
    } catch (IOException e) {
      throw new SlickException("Problem reading cursor image.", e);
    }
    
    //sets AWT cursor 
    this.setCursor( tk.createCustomCursor(img, hotspot, ref) ); 
  }
  
  public Preferences getPrefs() { 
    return prefs;
  }
  
  protected void cleanup() {
    super.cleanup();
    if (getPrefs()!=null) {
        if (currentDir!=null)
            getPrefs().putInt("direction.index", currentDir.ordinal());
        if (brushCombo!=null)
            getPrefs().putInt("brush.index", brushCombo.getSelectedIndex());
    }
  }
  
  private void changeBrush() {
    TileType type = TileType.values()[brushCombo.getSelectedIndex()];
    
    portMgr.cancelMove();
          
    switch (type) {
      case GROUND:
        current = tileFactory.getGround();
        break;
      case START:
        current = tileFactory.getStart();
        break;
      case END:
        current = tileFactory.getEnd();
        break;
      case WALL:
        current = tileFactory.getWall();
        break;
      case BOOST:
        current = tileFactory.getBoost(currentDir);
        break;
      case PORT:
        current = tileFactory.getPort(currentDir);
        break;
    }
    
    if (dirLabel!=null)
      dirLabel.setEnabled(current instanceof Directional);
  }
  
  private void rotateCurrent() {
    Direction old = currentDir;
    if (current!=null&&current instanceof Directional) {
      Direction d = ((Directional)current).getDirection().rotate(); 
      
      if (current.getTileType()==TileType.BOOST) {
        current = tileFactory.getBoost(d);
      }
      else if (current.getTileType()==TileType.PORT) {
        current = tileFactory.getPort(d);
      }
      currentDir = d;
    }
    
    if (old!=null && old!=currentDir) {
      dirLabel.setText("Direction: "+currentDir.toString());
    }
  }
  
  private void initMenuBar() {
    String str = "";
    
    final JMenuItem newItem = newJMenuItem("New", new ActionListener() { 
      public void actionPerformed(ActionEvent e) {
        newMap();
      }
    });
    newItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 
                InputEvent.CTRL_MASK));
    
    final JMenuItem open = newJMenuItem("Open", new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        openMap();
      }
    });
    open.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 
                InputEvent.CTRL_MASK));
    
    
    final JMenuItem save = newJMenuItem("Save", new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        saveMap(); 
      }
    });
    save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, 
                InputEvent.CTRL_MASK));
    
    
    final JMenuItem saveAs = newJMenuItem("Save As...", new ActionListener() { 
      public void actionPerformed(ActionEvent e) {
        saveMapAs();
      }
    });
    saveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
                InputEvent.CTRL_MASK|InputEvent.ALT_MASK ));
        
    final JMenuItem editInfo = newJMenuItem("Map Info", new ActionListener() { 
      public void actionPerformed(ActionEvent e) {
        infoDialog.showDialog();
      }
    });
       
    final JMenuItem clearMap = newJMenuItem("Clear Map", new ActionListener() { 
      public void actionPerformed(ActionEvent e) {
        if (canvas.map.getData().isEmpty())
          return;
        
        int i = JOptionPane.showConfirmDialog(LevelEditor.this,
                                "Are you sure you wish to clear the map?",
                                "", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
        if (i==JOptionPane.YES_OPTION) {
          clearMap(); 
        }
      }
    });
     
    final JMenu menu = new JMenu("File");
    menu.add(newItem);
    menu.add(open);
    menu.add(save);
    menu.add(saveAs);
    menu.addSeparator();
    menu.add(newQuitMenuItem()); 
    
    final JMenu edit = new JMenu("Edit");
    edit.add(editInfo);
    edit.add(clearMap);
    
    str = ResourceManager.getString("ui.menu.help", "Help");
    final JMenu help = new JMenu(str);
    help.add(newAboutMenuItem());
    
    getJMenuBar().add(menu);
    getJMenuBar().add(edit);
    getJMenuBar().add(help);
  }
  
  private void updateTitle() {
    String prefix = getEditorName()+" - ";
    
    String name = (currentFile!=null)
                    ? currentFile.getName()
                    : "untitled";
    
    String suffix = (fileChanged) ? " *" : "";
    setTitle(prefix + name + suffix);
  }
  
  private void newMap() {
    boolean doNew = true;
    boolean valid = true;
    
    if (fileChanged) {
      String name = (currentFile!=null) 
                        ? currentFile.getName()
                        : "untitled";
                        
        
      int i = JOptionPane.showConfirmDialog(this, 
                            "Save changes to "+name+"?", "",
                            JOptionPane.YES_NO_CANCEL_OPTION, 
                            JOptionPane.QUESTION_MESSAGE);
      
      if (i!=JOptionPane.YES_OPTION && i!=JOptionPane.NO_OPTION)
        doNew = false;
        
      //save changes before clearing
      if (i==JOptionPane.YES_OPTION) {
        valid = saveMap();
      }
    }
    
    //if we should clear the map
    if (doNew && valid) {
      infoDialog.clearInfo();
      clearMap();
      fileChanged = false;
      currentFile = null;
    }
    updateTitle();
  }
  
  private void clearMap() {    
    for (int x=0; x<LevelMap.WIDTH; x++) {
      for (int y=0; y<LevelMap.HEIGHT; y++) {
        if (canvas.map!=null) {
          fileChanged = true;
          canvas.map.putTile(tileFactory.getGround(), x, y);
        }
      }
    }
    updateTitle();
    startDown = false;
  }
  
  private boolean openMap() {
    boolean opened = false;
    
    File f = getFile("Load");
    if (f!=null) {
      try {
        LevelData d = mapStream.fromXML(new FileInputStream(f));
        canvas.map.setData(d);
        fileChanged = false;
        currentFile = f;
        updateTitle();
        opened = true;
      } //end try
      catch (FileNotFoundException e) {
        Log.warn("Can't find file: "+e.getMessage());
        JOptionPane.showMessageDialog(this,  
                      "Can't find file.",
                      "Error", JOptionPane.ERROR_MESSAGE);
      } //end catch
      catch (Exception e) {
        Log.warn("Problem reading data: "+e.getMessage());
        JOptionPane.showMessageDialog(this,  
                      "Not a valid BallPuzzle map file.",
                      "Error", JOptionPane.ERROR_MESSAGE);
      } //end catch
    } //end if
    
    return opened;
  }
  
  private boolean saveMap() {
    if (currentFile!=null) {
      return trySave(currentFile, false);
    }
    else {
      return saveMapAs();
    }
  }
  
  private boolean saveMapAs() {
    getJFileChooser().setSelectedFile(currentFile);
    File f = getFile("Save As");
    return trySave(f, true);
  }
  
  private boolean trySave(File f, boolean checkRepl) {
    boolean saved = false;
    
    if (f!=null) {
      if (getJFileChooser().getFileFilter() instanceof BPuzFileFilter) {
        f = toExtension(f, "bpuz");
      }
        
      //starts as false if we are checking replace
      //otherwise starts as true and skips exist check
      boolean save = false;
      
      //if we should check for replacement
      if (!checkRepl) {
        save = true;
      }
      //if we are checking and the file exists
      else if (f.exists()) {
        int i = JOptionPane.showConfirmDialog(this, 
                            "File already exists. Replace?", "Warning",
                            JOptionPane.YES_NO_OPTION, 
                            JOptionPane.WARNING_MESSAGE);
        save = (i==JOptionPane.YES_OPTION);
      }
      else {
        save = true;
      }
      
      if (save) {
        try {
          mapStream.toXML(canvas.map.getData(), new FileOutputStream(f));
          Log.info("Saved file "+f.getName());
          fileChanged = false;
          currentFile = f;
          updateTitle();
          saved = true;
        }
        catch (Exception e) {
          Log.warn("Problem saving file: "+e.getMessage());
          JOptionPane.showMessageDialog(this,  
                        "Problem saving file: "+e.getMessage(),
                        "Error", JOptionPane.ERROR_MESSAGE);
        } //end catch
      } //end if save
    } //end if non-null
    
    return saved;
  }
  
  public String getEditorName() {
    return "Bpuz Level Editor";
  }
  
  /**
   *
   */ 
  public void handleMouseEvent(boolean placing, int x, int y) {
    //get index of clicked tile
    int xIndex = (int)(x / Tile.WIDTH);
    int yIndex = (int)(y / Tile.HEIGHT);
    
    if (xIndex>LevelMap.WIDTH-1 || xIndex<0 
            || yIndex>LevelMap.HEIGHT-1 || yIndex<0) {
      return;
    }
    
    fileChanged = true;
    updateTitle();
    
    //get map
    LevelMap map = canvas.map;
    
    //get old brush
    Tile old = map.getTile(xIndex, yIndex);
    
    if (current.getTileType()==TileType.START) {
      if (placing) {
        int sx = map.getStartX();
        int sy = map.getStartY();
        
        if (map.getTile(sx, sy).getTileType()==TileType.START) {
          map.putTile(tileFactory.getGround(), sx, sy);
        }
        clearTile(old, xIndex, yIndex);
        map.putTile(current, xIndex, yIndex);
      }
      else {
        clearTile(old, xIndex, yIndex);
      }
    }
    else if (current.getTileType()==TileType.PORT) {
      if (placing) {
        portMgr.handlePlace(x, y);
      }
      else {
        //if we should cancel the port place
        if (portMgr.port1 != null) {
          portMgr.cancelMove();
        }
        else {
          portMgr.showPair = false;
          clearTile(old, xIndex, yIndex);
        }
      }
    }
    //using any other brush...
    else {
      if (placing) {
        clearTile(old, xIndex, yIndex);
        map.putTile(current, xIndex, yIndex);
      }
      else {
        clearTile(old, xIndex, yIndex);
      }
    }
  }
  
  private void clearTile(Tile t, int xIndex, int yIndex) {
    if (canvas.map==null || t==null)
      return;
      
    if (t.getTileType()==TileType.PORT) {
      portMgr.clearLinked(t, xIndex, yIndex);
    }
    else {
      canvas.map.putTile(tileFactory.getGround(), xIndex, yIndex);
    }
  }
  
  private class EditorCanvas extends SlickCanvas {
    
    private LevelMap map = null;
    private int x=-1, y=-1;
    
    public EditorCanvas() throws LWJGLException {
      super();
      addMouseMotionListener(new MouseMotionListener() {
        public void mouseMoved(MouseEvent e) {
          x = e.getX();
          y = e.getY();
        }
        
        public void mouseDragged(MouseEvent e) {
          x = e.getX();
          y = e.getY();
        }
      });
      addMouseListener(new MouseAdapter() {
        public void mouseExited(MouseEvent e) {
          x = -1;
          y = -1;
          portMgr.showLink = false;
        }
      });
    }
    
    public void initSystem() throws SlickException { 
      tileFactory = tileFactory.getEditorInstance();
      tileFactory.init();
      
      mapStream = new MapStream(tileFactory);
      
      changeBrush();
      
      map = new LevelMap();
      
      setSlickBackground(Color.gray.darker());    
    }
    
    public void render(Graphics g) {
      Color old = g.getColor();
      map.render(g);
      
      g.setColor(LevelMap.GRID_COLOR);
      g.drawRect(0, 0, LevelMap.PIXEL_WIDTH, LevelMap.PIXEL_HEIGHT);
      
      if (portMgr.showLink) {
        portMgr.drawLink(g);
      }
      
      if (portMgr.showPair) {
        portMgr.drawPair(g);
      }
      
      if (current!=null && x>=0 && y>=0) {
      	current.render(g, x-Tile.WIDTH/2, y-Tile.HEIGHT/2);
      	g.drawRect(x-Tile.WIDTH/2, y-Tile.HEIGHT/2, Tile.WIDTH, Tile.HEIGHT);
      }
      
      g.setColor(old);
    }
    
    public void update(int delta) { 
      map.update(delta);
    }
  }
  
  private class PortManager {
    private int oxin=0, oyin=0;
    private Tile port1 = null;
    private Tile port2 = null;
    private boolean showPair = false;
    
    private int lx1=0, lx2=0, ly1=0, ly2=0;
    private boolean showLink=false;
    
    private Rectangle pair1 = new Rectangle(0,0,Tile.WIDTH,Tile.HEIGHT);
    private Rectangle pair2 = new Rectangle(0,0,Tile.WIDTH,Tile.HEIGHT);
    
    /** Clears the tile and it's link if they are ports. */
    public void clearLinked(Tile p1, int xIndex, int yIndex) {
      LevelMap map = canvas.map;
      
      if (map==null || p1==null || tileFactory==null)
        return;
        
      if (p1.getTileType()==TileType.PORT) {
        map.putTile(tileFactory.getGround(), xIndex, yIndex);
        
        int lx = ((PortTile)p1).getLinkX();
        int ly = ((PortTile)p1).getLinkY();
          
        Tile p2 = map.getTile(lx, ly);
        if (p2.getTileType()==TileType.PORT) {
          map.putTile(tileFactory.getGround(), lx, ly);
        }
      }
    }
    
    public void cancelMove() {
      if (port1==null)
        return;
      
      clearTile(port1, oxin, oyin);
      port1 = null;
      port2 = null;
      showLink = false;
      showPair = false;
    }
    
    public void handlePlace(int x, int y) {
      //get index of clicked tile
      int xIndex = (int)(x / Tile.WIDTH);
      int yIndex = (int)(y / Tile.HEIGHT);
    
      if (xIndex>LevelMap.WIDTH-1 || xIndex<0 
            || yIndex>LevelMap.HEIGHT-1 || yIndex<0 || tileFactory==null) {
        return;
      }
    
      fileChanged = true;
      updateTitle();
    
      //get map
      LevelMap map = canvas.map;
      Tile old = map.getTile(xIndex, yIndex);
      clearTile(old, xIndex, yIndex);
      
      //we're on our first
      if (port1==null) {
        port1 = current;
        
        oxin = xIndex;
        oyin = yIndex;
        
        map.putTile(port1, oxin, oyin);
        current = tileFactory.getPort(currentDir);
        
        showLink = true;
        showPair = false;
      }
      //we're on our second
      else if (port2==null) {
        if (old!=port1) {
          port2 = current;
          
          Direction d1 = ((PortTile)port1).getDirection();
          Direction d2 = ((PortTile)port2).getDirection();
                    
          ((PortTile)port1).setLinkAt(xIndex, yIndex);
          ((PortTile)port1).setLinkDirection(d2);
          
          ((PortTile)port2).setLinkAt(oxin, oyin);
          ((PortTile)port2).setLinkDirection(d1);
            
          map.putTile(port2, xIndex, yIndex);
          current = tileFactory.getPort(currentDir);
        }
        port1 = null;
        port2 = null;
        showLink = false;
        oxin = 0;
        oyin = 0;
      }
    }
    
    public void handleMouseMove(int x, int y) {
      int xIndex = (int)(x / Tile.WIDTH);
      int yIndex = (int)(y / Tile.HEIGHT);
      
      LevelMap map = canvas.map;
      
      if (xIndex>LevelMap.WIDTH-1 || xIndex<0 
            || yIndex>LevelMap.HEIGHT-1 || yIndex<0) { 
        showLink = false;
      }
      
      if (map==null)
        return;
      
      if (port1!=null) {        
        lx1 = oxin * Tile.WIDTH + Tile.WIDTH/2;
        ly1 = oyin * Tile.HEIGHT + Tile.HEIGHT/2;
        lx2 = xIndex * Tile.WIDTH + Tile.WIDTH/2;
        ly2 = yIndex * Tile.HEIGHT + Tile.HEIGHT/2;
        showLink = true;
      }
      else {
        lx1 = 0;
        ly1 = 0;
        lx2 = 0;
        ly2 = 0;
        showLink = false;
      }
     
      Tile hover1 = map.getTile(xIndex, yIndex);
      
      if (hover1.getTileType()==TileType.PORT) {
        pair1.setLocation(xIndex*Tile.WIDTH, yIndex*Tile.HEIGHT);
        
        int pair2x = ((PortTile)hover1).getLinkX();
        int pair2y = ((PortTile)hover1).getLinkY();
        
        Tile hover2 = map.getTile(pair2x, pair2y);
        if (hover2.getTileType()==TileType.PORT) {
          pair2.setLocation(pair2x*Tile.WIDTH, pair2y*Tile.HEIGHT);
          showPair = true;
        }
      }
      else {
        showPair = false;
      }
    }
    
    public void drawPair(Graphics g) {
      Color old = g.getColor();
      g.setColor(Color.red);
      g.drawRect(pair1.x, pair1.y, pair1.width, pair1.height);
      g.drawRect(pair2.x, pair2.y, pair2.width, pair2.height);
      g.setColor(old);
    }
    
    public void drawLink(Graphics g) {
      Color old = g.getColor();
      g.setColor(Color.red);
      g.drawLine(lx1, ly1, lx2, ly2);
      g.setColor(old);
    }
  } //end port manager class
  
  
  private class LevelInfoDialog {
    
    JTextField name,author,desc;
    JPanel main;
    
    public LevelInfoDialog() {
      JLabel l1 = new JLabel("Map Name:");
      JLabel l2 = new JLabel("Author:");
      JLabel l3 = new JLabel("Map Description:");
      
      name = new JTextField(8);
      author = new JTextField(8);
      desc = new JTextField(8);
      
      main = new JPanel(new SpringLayout());
      main.add(l1);
      main.add(name);
      main.add(l2);
      main.add(author);
      main.add(l3);
      main.add(desc);
      
      SpringUtilities.makeCompactGrid(main,
                                      3, 2, //rows, cols
                                      6, 6, //initX, initY
                                      6, 6);//xPad, yPad
    }
    
    public void clearInfo() {
      name.setText("");
      author.setText("");
      desc.setText("");
      
      LevelData d = canvas.map.getData();
      d.setName("");
      d.setAuthor("");
      d.setDescription("");
    }
    
    public int showDialog() {
      LevelData data = canvas.map.getData();
      
      name.setText( data.getName() );
      author.setText( data.getAuthor() );
      desc.setText( data.getDescription() );
      
      String old1 = name.getText();
      String old2 = author.getText();
      String old3 = desc.getText();
      
      int i = JOptionPane.showConfirmDialog(LevelEditor.this,
                          main, "Map Info", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
      if (i==JOptionPane.OK_OPTION) {
        data.setName(name.getText());
        data.setAuthor(author.getText());
        data.setDescription(desc.getText());
        
        if (!old1.equals(name.getText())
                ||!old2.equals(author.getText())
                ||!old3.equals(desc.getText())) {
          fileChanged = true;
          updateTitle();    
        }
      }
      return i;
    }
  }
}